package com.audaque.springboot.foshanupload.core.util;

import com.audaque.springboot.foshanupload.core.model.co.PointLonLatMapCo;
import com.audaque.springboot.foshanupload.core.model.co.RegionRangeMapCo;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;

import static java.lang.Math.PI;

/**   经纬度地图工具
 * @author zgb
 * @desc ...
 * @date 2021-07-07 17:39:43
 */

public class LogLatMapUtil {
    /**
     * 地球赤道半径(km)
     * */
    public final static double EARTH_RADIUS = 6378.137;
    /**
     * 地球每度的弧长(km)
     * */
    public final static double EARTH_ARC = 111.199;

    /**
     * 转化为弧度(rad)
     * */
    public static double rad(double d) {
        return d * Math.PI / 180.0;
    }


    private static Double rad(Double d) {
        return d * Math.PI / 180.0;
    }



    /**
     * 求两经纬度距离
     *
     * @param lon1
     *            第一点的经度
     * @param lat1
     *            第一点的纬度
     * @param lon2
     *            第二点的经度
     * @param lat2
     *            第二点的纬度
     * @return 两点距离，单位km
     * */
    public static double GetDistanceOne(double lon1, double lat1, double lon2,
                                        double lat2) {
        double r1 = rad(lat1);
        double r2 = rad(lon1);
        double a = rad(lat2);
        double b = rad(lon2);
        double s = Math.acos(Math.cos(r1) * Math.cos(a) * Math.cos(r2 - b)
                + Math.sin(r1) * Math.sin(a))
                * EARTH_RADIUS;
        return s;
    }

    /**
     * 求两经纬度距离(google maps源码中)
     *
     * @param lon1
     *            第一点的经度
     * @param lat1
     *            第一点的纬度
     * @param lon2
     *            第二点的经度
     * @param lat2
     *            第二点的纬度
     * @return 两点距离，单位km
     * */
    public static double GetDistanceTwo(double lon1, double lat1, double lon2,
                                        double lat2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lon1) - rad(lon2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
                + Math.cos(radLat1) * Math.cos(radLat2)
                * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        return s;
    }

    /**
     * 求两经纬度距离
     *
     * @param lon1
     *            第一点的经度
     * @param lat1
     *            第一点的纬度
     * @param lon2
     *            第二点的经度
     * @param lat2
     *            第二点的纬度
     * @return 两点距离，单位km
     * */
    public static double GetDistanceThree(double lon1, double lat1,
                                          double lon2, double lat2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double radLon1 = rad(lon1);
        double radLon2 = rad(lon2);
        if (radLat1 < 0)
            radLat1 = Math.PI / 2 + Math.abs(radLat1);// south
        if (radLat1 > 0)
            radLat1 = Math.PI / 2 - Math.abs(radLat1);// north
        if (radLon1 < 0)
            radLon1 = Math.PI * 2 - Math.abs(radLon1);// west
        if (radLat2 < 0)
            radLat2 = Math.PI / 2 + Math.abs(radLat2);// south
        if (radLat2 > 0)
            radLat2 = Math.PI / 2 - Math.abs(radLat2);// north
        if (radLon2 < 0)
            radLon2 = Math.PI * 2 - Math.abs(radLon2);// west
        double x1 = Math.cos(radLon1) * Math.sin(radLat1);
        double y1 = Math.sin(radLon1) * Math.sin(radLat1);
        double z1 = Math.cos(radLat1);

        double x2 = Math.cos(radLon2) * Math.sin(radLat2);
        double y2 = Math.sin(radLon2) * Math.sin(radLat2);
        double z2 = Math.cos(radLat2);

        double d = Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)
                + Math.pow((z1 - z2), 2);
        // // 余弦定理求夹角
        // double theta = Math.acos((2 - d) / 2);

        d = Math.pow(EARTH_RADIUS, 2) * d;
        // //余弦定理求夹角
        double theta = Math.acos((2 * Math.pow(EARTH_RADIUS, 2) - d)
                / (2 * Math.pow(EARTH_RADIUS, 2)));

        double dist = theta * EARTH_RADIUS;
        return dist;
    }

    /**
     * 求两经纬度方向角
     *
     * @param lon1
     *            第一点的经度
     * @param lat1
     *            第一点的纬度
     * @param lon2
     *            第二点的经度
     * @param lat2
     *            第二点的纬度
     * @return 方位角，角度（单位：°）
     * */
    public static double GetAzimuth(double lon1, double lat1, double lon2,
                                    double lat2) {
        lat1 = rad(lat1);
        lat2 = rad(lat2);
        lon1 = rad(lon1);
        lon2 = rad(lon2);
        double azimuth = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1)
                * Math.cos(lat2) * Math.cos(lon2 - lon1);
        azimuth = Math.sqrt(1 - azimuth * azimuth);
        azimuth = Math.cos(lat2) * Math.sin(lon2 - lon1) / azimuth;
        azimuth = Math.asin(azimuth) * 180 / Math.PI;
        if (Double.isNaN(azimuth)) {
            if (lon1 < lon2) {
                azimuth = 90.0;
            } else {
                azimuth = 270.0;
            }
        }
        return azimuth;
    }

    /**
     * 已知一点经纬度A，和与另一点B的距离和方位角，求B的经纬度(计算结果有误)
     *
     * @param lon1
     *            A的经度
     * @param lat1
     *            A的纬度
     * @param distance
     *            AB距离（单位：米）
     * @param azimuth
     *            AB方位角
     * @return B的经纬度
     * */
    public static String GetOtherPoint(double lon1, double lat1,
                                       double distance, double azimuth) {
        azimuth = rad(azimuth);
        double ab = distance / EARTH_ARC;// AB间弧线长
        ab = rad(ab);
        double Lat = Math.asin(Math.sin(lat1) * Math.cos(ab) + Math.cos(lat1)
                * Math.sin(ab) * Math.cos(azimuth));
        double Lon = lon1
                + Math.asin(Math.sin(azimuth) * Math.sin(ab) / Math.cos(Lat));
        System.out.println(Lon + "," + Lat);

        double a = Math.acos(Math.cos(90 - lon1) * Math.cos(ab)
                + Math.sin(90 - lon1) * Math.sin(ab) * Math.cos(azimuth));
        double C = Math.asin(Math.sin(ab) * Math.sin(azimuth) / Math.sin(a));
        System.out.println("c=" + C);
        double lon2 = lon1 + C;
        double lat2 = 90 - a;
        return lon2 + "," + lat2;
    }

    /**
     * 已知一点经纬度A，和与另一点B的距离和方位角，求B的经纬度
     *
     * @param lng1
     *            A的经度
     * @param lat1
     *            A的纬度
     * @param distance
     *            AB距离（单位：米）
     * @param azimuth
     *            AB方位角
     * @return B的经纬度
     * */
    public static String ConvertDistanceToLogLat(double lng1, double lat1,
                                                 double distance, double azimuth) {
        azimuth = rad(azimuth);
        // 将距离转换成经度的计算公式
        double lon = lng1 + (distance * Math.sin(azimuth))
                / (EARTH_ARC * Math.cos(rad(lat1)));
        // 将距离转换成纬度的计算公式
        double lat = lat1 + (distance * Math.cos(azimuth)) / EARTH_ARC;
        return lon + "," + lat;
    }




    /**
     * 判断是否在多边形区域内
     *
     * @param pointLonLatMapCo 要判断的点的横坐标
     * @param lon         区域各顶点的横坐标数组
     * @param lat         区域各顶点的纵坐标数组
     * @return
     */
    public static boolean isInPolygon(PointLonLatMapCo pointLonLatMapCo, Double[] lon,
                                      Double[] lat) {
        Double pointLon = pointLonLatMapCo.getLongitude();
        Double pointLat = pointLonLatMapCo.getLatitude();
        // 将要判断的横纵坐标组成一个点
        Point2D.Double point = new Point2D.Double(pointLon, pointLat);
        // 将区域各顶点的横纵坐标放到一个点集合里面
        List<Point2D.Double> pointList = new ArrayList<Point2D.Double>();
        Double polygonPoint_x = 0.0, polygonPoint_y = 0.0;
        for (Integer i = 0; i < lon.length; i++) {
            polygonPoint_x = lon[i];
            polygonPoint_y = lat[i];
            Point2D.Double polygonPoint = new Point2D.Double(polygonPoint_x, polygonPoint_y);
            pointList.add(polygonPoint);
        }
        return check(point, pointList);
    }

    /**
     * @param point   要判断的点的横纵坐标
     * @param polygon 组成的顶点坐标集合
     * @return
     */
    private static boolean check(Point2D.Double point, List<Point2D.Double> polygon) {
        java.awt.geom.GeneralPath peneralPath = new java.awt.geom.GeneralPath();

        Point2D.Double first = polygon.get(0);
        // 通过移动到指定坐标（以双精度指定），将一个点添加到路径中
        peneralPath.moveTo(first.x, first.y);
        polygon.remove(0);
        for (Point2D.Double d : polygon) {
            // 通过绘制一条从当前坐标到新指定坐标（以双精度指定）的直线，将一个点添加到路径中。
            peneralPath.lineTo(d.x, d.y);
        }
        // 将几何多边形封闭
        peneralPath.lineTo(first.x, first.y);
        peneralPath.closePath();
        // 测试指定的 Point2D 是否在 Shape 的边界内。
        return peneralPath.contains(point);
    }

    /**
     * 通过经纬度获取距离(单位：米)
     *
     * @param pointLonLatMapCo1 点1
     * @param pointLonLatMapCo2 点2
     * @return 距离
     */
    public static Double getDistance(PointLonLatMapCo pointLonLatMapCo1, PointLonLatMapCo pointLonLatMapCo2) {
        Double lat1 = pointLonLatMapCo1.getLatitude();
        Double lon1 = pointLonLatMapCo1.getLongitude();
        Double lat2 = pointLonLatMapCo2.getLatitude();
        Double lon2 = pointLonLatMapCo2.getLongitude();
        Double radLat1 = rad(lat1);
        Double radLat2 = rad(lat2);
        Double a = radLat1 - radLat2;
        Double radLon1 = rad(lon1);
        Double radLon2 = rad(lon2);
        Double b = radLon1 - radLon2;
        Double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
                Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS*1000;
        return s;
    }


    /**
     * @param pointLonLatMapCo1 点1
     * @param pointLonLatMapCo2 点2
     * @param radius       判断一个点是否在圆形区域内,比较坐标点与圆心的距离是否小于半径
     */
    public static boolean isInCircle(Double radius, PointLonLatMapCo pointLonLatMapCo1, PointLonLatMapCo pointLonLatMapCo2) {
        Double lat1 = pointLonLatMapCo1.getLatitude();
        Double lon1 = pointLonLatMapCo1.getLongitude();
        Double lat2 = pointLonLatMapCo2.getLatitude();
        Double lon2 = pointLonLatMapCo2.getLongitude();
        Double distance = getDistance(pointLonLatMapCo1, pointLonLatMapCo2);
        if (distance > radius) {
            return false;
        } else {
            return true;
        }
    }


    /**
     * 计算地球上任意两点(经纬度)距离
     *
     * @param pointLonLatMapCo1 点1
     * @param pointLonLatMapCo2 点2
     * @return 返回距离 单位：米
     */
    public static Double distanceByLongNLat(PointLonLatMapCo pointLonLatMapCo1, PointLonLatMapCo pointLonLatMapCo2) {
        Double lat1 = pointLonLatMapCo1.getLatitude();
        Double lon1 = pointLonLatMapCo1.getLongitude();
        Double lat2 = pointLonLatMapCo2.getLatitude();
        Double lon2 = pointLonLatMapCo2.getLongitude();
        Double a, b, R;
        R = EARTH_RADIUS*1000;//地球半径
        lat1 = lat1 * PI / 180.0;
        lat2 = lat2 * PI / 180.0;
        a = lat1 - lat2;
        b = (lon1 - lon2) * PI / 180.0;
        Double d;
        Double sa2, sb2;
        sa2 = Math.sin(a / 2.0);
        sb2 = Math.sin(b / 2.0);
        d = 2 * R * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(lat1) * Math.cos(lat2) * sb2 * sb2));
        return d;
    }

    /**
     * 根据经纬度和半径计算经纬度范围
     *
     * @param raidus 单位米
     * @return minLat, minLng, maxLat, maxLng
     */
    public static RegionRangeMapCo getRegionRangeCo(PointLonLatMapCo pointLonLatMapCo, Double raidus) {
        Double lat = pointLonLatMapCo.getLatitude();
        Double lon = pointLonLatMapCo.getLongitude();
        RegionRangeMapCo regionRangeMapCo = new RegionRangeMapCo();
        Double latitude = lat;
        Double longitude = lon;

        Double degree = (24901 * 1609) / 360.0;
        Double raidusMile = raidus;

        Double dpmLat = 1 / degree;
        Double radiusLat = dpmLat * raidusMile;
        Double minLat = latitude - radiusLat;
        Double maxLat = latitude + radiusLat;

        Double mpdLng = degree * Math.cos(latitude * (PI / 180));
        Double dpmLng = 1 / mpdLng;
        Double radiusLng = dpmLng * raidusMile;
        Double minLng = longitude - radiusLng;
        Double maxLng = longitude + radiusLng;
        regionRangeMapCo.setMaxLat(maxLat);
        regionRangeMapCo.setMaxLng(maxLng);
        regionRangeMapCo.setMinLat(minLat);
        regionRangeMapCo.setMinLng(minLng);
        return regionRangeMapCo;
    }
    /**
     * test
     * @param args
     */
    public static void test1(String[] args){
        PointLonLatMapCo pointLonLatMapCo1 = new PointLonLatMapCo();
        pointLonLatMapCo1.setLongitude(116.309344);
        pointLonLatMapCo1.setLatitude(39.967892);
        PointLonLatMapCo pointLonLatMapCo2 = new PointLonLatMapCo();
        pointLonLatMapCo2.setLongitude(116.309581);
        pointLonLatMapCo2.setLatitude(39.966805);
        double distance = getDistance(pointLonLatMapCo1, pointLonLatMapCo2);
        System.out.println(distance);
    }


    public static void test2(String[] args) {
        double lon1 = 116.309344;
        double lat1 = 39.967892;
        double lon2 = 116.309581;
        double lat2 = 39.966805;

        double distance1 = GetDistanceOne(lon1, lat1, lon2, lat2);
        double distance2 = GetDistanceTwo(lon1, lat1, lon2, lat2);
        double azimuth = GetAzimuth(lon1, lat1, lon2, lat2);
        System.out.println("经纬度为(" + lon1 + "," + lat1 + ")的点与经纬度为(" + lon2
                + "," + lat2 + ")相距：" + distance2 + "千米," + "方位角：" + azimuth
                + "°");
        System.out.println("距经纬度为(" + lon1 + "," + lat1 + ")的点" + distance2
                + "千米,方位角为" + azimuth + "°的另一点经纬度为("
                + ConvertDistanceToLogLat(lon1, lat1, distance2, azimuth) + ")");
    }





}
